package xyz.xtt.pay.service.impl;

import static xyz.xtt.pay.utils.AssertUtils.isFalse;
import static xyz.xtt.pay.utils.AssertUtils.isNull;
import static xyz.xtt.pay.utils.AssertUtils.isTrue;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import com.google.common.primitives.Longs;

import cn.hutool.core.util.IdUtil;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import xyz.xtt.asset.api.enums.OwnerTypeEnum;
import xyz.xtt.asset.api.enums.ReferProtocolEnum;
import xyz.xtt.asset.api.enums.SecondEnum;
import xyz.xtt.asset.api.enums.SellPatternEnum;
import xyz.xtt.asset.client.request.CancelTransactionRequest;
import xyz.xtt.asset.client.request.ConfirmTransactionRequest;
import xyz.xtt.asset.client.request.MakeTransactionRequest;
import xyz.xtt.asset.client.response.AssetAllInfoResponse;
import xyz.xtt.asset.client.response.AssetExtInfoResponse;
import xyz.xtt.asset.client.response.AssetInfoResponse;
import xyz.xtt.asset.client.response.AssetTransactionResponse;
import xyz.xtt.asset.client.response.AssetUserLogResponse;
import xyz.xtt.asset.client.response.TransactionResponse;
import xyz.xtt.equity.api.enums.BenefitEnum;
import xyz.xtt.equity.client.request.TblUserBenefitDto;
import xyz.xtt.pay.api.dto.PayAttach;
import xyz.xtt.pay.api.dto.SplitAccountItem;
import xyz.xtt.pay.api.enums.PayOrderBusinessTypeEnum;
import xyz.xtt.pay.api.enums.PayOrderStatusEnum;
import xyz.xtt.pay.constants.ErrorMessage;
import xyz.xtt.pay.dto.pay.GoodsInfo;
import xyz.xtt.pay.entity.TblPayOrder;
import xyz.xtt.pay.entity.TblPayOrderLog;
import xyz.xtt.pay.enums.DealStateEnum;
import xyz.xtt.pay.exception.ChainException;
import xyz.xtt.pay.service.IInnerService;
import xyz.xtt.pay.service.INftOrderService;
import xyz.xtt.pay.service.IPayOrderOperator;
import xyz.xtt.pay.service.IRocketMqService;
import xyz.xtt.pay.service.ITblPayLimitService;
import xyz.xtt.pay.service.ITblPayOrderLogService;
import xyz.xtt.pay.service.ITblPayOrderService;
import xyz.xtt.pay.support.PayCenterHttpService;
import xyz.xtt.pay.utils.RedisLockUtil;
import xyz.xtt.pay.vo.CreateOrderRequest;
import xyz.xtt.pay.vo.PayCallbackMessage;
import xyz.xtt.pay.vo.PayCenterArgsResponse;
import xyz.xtt.user.client.response.UserInfoResponse;

/**
 * 平台和个人1155协议资产的售卖，包括：数字盲盒（平台发售的1155协议资产），用户自己铸造的1155协议资产
 * 
 * @author dale
 * @date 2025/2/10
 **/
@Component
@Slf4j
public class Asset1155BuyPayOrderOperator implements IPayOrderOperator {

	public static final Integer SPLIT_ACCOUNT_PERCENTAGE = 70;
	public static final Integer SECOND_TRADE_SPLIT_ACCOUNT_PERCENTAGE = 85;
	@Autowired
	private PayCenterHttpService payCenterHttpService;
	@Autowired
	private RedisLockUtil redisLockUtil;
	@Autowired
	private ITblPayOrderService iTblPayOrderService;
	@Autowired
	private INftOrderService nftOrderService;
	@Autowired
	private IRocketMqService rocketMqService;
	@Autowired
	private ITblPayOrderLogService iTblPayOrderLogService;
	@Autowired
	private ITblPayLimitService payLimitService;

	@Value("${personal.mint.order.price}")
	private String personalMintPrice;

	@Autowired
	private IInnerService iInnerService;

	@Override
	public boolean canOperateType(PayOrderBusinessTypeEnum type) {
		return PayOrderBusinessTypeEnum.BUY_ASSET_1155.equals(type) || PayOrderBusinessTypeEnum.C_TO_C.equals(type);
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public PayCenterArgsResponse createPayOrder(UserInfoResponse tblUser, PayOrderBusinessTypeEnum orderTypeEnum, CreateOrderRequest request,
			GoodsInfo goodsInfo) {
		isNull(request.getAssetId(), ErrorMessage.ARGUMENT_NOT_VALID);
		long assetId = Long.parseLong(request.getAssetId());
		AssetAllInfoResponse assetResponse = iInnerService.allInfoAssetId(assetId);
		isNull(assetResponse, ErrorMessage.ASSET_NOT_EXIST);
		AssetInfoResponse asset = assetResponse.getAsset();
		isNull(asset, ErrorMessage.ORDER_CREATE_FAILED, () -> log.error("createPayOrder get asset expand failed assetId:{}", assetId));
		isFalse(asset.isAssetNft(), ErrorMessage.ASSET_NOT_EXIST);
		AssetExtInfoResponse assetExt = assetResponse.getAssetExt();
		isNull(assetExt, ErrorMessage.ASSET_NOT_EXIST);
		isFalse(Objects.equals(assetExt.getReferProtocol(), ReferProtocolEnum.ERC_1155.getDescription()), ErrorMessage.UPSUPPORTED_PROTOCOL_TYPE);
		isTrue(Objects.equals(asset.getAuthorId(), String.valueOf(tblUser.getAppUserId())), ErrorMessage.CAN_NOT_BUY_SELF);
		OwnerTypeEnum ownerTypeEnum = OwnerTypeEnum.queryConvert(asset.getOwnerType());
		boolean isC2C = Objects.equals(ownerTypeEnum, OwnerTypeEnum.USER);
		// 资产售卖状态校验
		isFalse(Objects.equals(asset.getSellPattern(), SellPatternEnum.BUY_NOW_PRICE.code()), ErrorMessage.ASSET_STATUS_EX);
		AssetUserLogResponse assetUserLog = iInnerService.lastConfig(assetResponse.getAsset().getId());
		// 个人售卖需要进行配置价格校验、公示判断
		Integer secondType = asset.getSecondType();
		if (isC2C) {
			// 未完成公示不可售卖
			if (!Objects.equals(SecondEnum.MINT_POWER.getCode(), secondType)) {
				isTrue(assetExt.getPublicityPeriodStart() == null || assetExt.getPublicityPeriodEnd() == null || assetExt.getPublicityPeriodStart() == 0L
						|| assetExt.getPublicityPeriodEnd() == 0L
						|| assetExt.getPublicityPeriodEnd() > System.currentTimeMillis(), ErrorMessage.ASSET_NOT_PUBLICIZED);
			}
			isFalse(checkAssetBeginSale(assetExt), ErrorMessage.ASSET_NOT_START_SALE);
			isFalse(assetUserLog != null && assetUserLog.getId() != null, ErrorMessage.ASSET_CONFIG_LOG_NOT_EXIST);
			isFalse(assetUserLog.getCurrPrice().compareTo(asset.getPrice()) == 0
					&& assetUserLog.getCurrGuidePrice().compareTo(asset.getGuidePrice()) == 0, ErrorMessage.ASSET_PRICE_CHANGED);
			orderTypeEnum = PayOrderBusinessTypeEnum.C_TO_C;
		}

		// 预售购买权限校验
		boolean isPreSale = assetExt.getPreSellStartDatetime() != null && assetExt.getSellStartDatetime() != null
				&& assetExt.getPreSellStartDatetime() < assetExt.getSellStartDatetime() && assetExt.getPreSellStartDatetime() <= System.currentTimeMillis()
				&& assetExt.getSellStartDatetime() > System.currentTimeMillis();
		if (isPreSale) {
			if (!iInnerService.hasGift365(tblUser.getAppUserId()) && !hasPrePurchaseBenefit(tblUser.getAppUserId())) {
				throw new ChainException(ErrorMessage.NEED_PRE_PURCHASE_BENEFIT);
			}
		}
		goodsInfo.setGoodsId(String.valueOf(asset.getId()));
		goodsInfo.setGoodsName(asset.getAssetName());
		goodsInfo.setGoodsNums(request.getPurchaseAmount());
		goodsInfo.setGoodsPrice(asset.getPrice());
		goodsInfo.setGoodsType("0");
		// 分布式锁同步（商品+用户）
		String lockKey = String.format(RedisLockUtil.LOCK_KEY_USER_PURCHASE, tblUser.getAppUserId(), request.getAssetId());
		String lockValue = UUID.randomUUID().toString();
		boolean lock = false;
		try {
			lock = redisLockUtil.acquireLock(lockKey, lockValue, 1, TimeUnit.MINUTES);
			if (lock) {
				TransactionResponse notPayAssetTransaction = iInnerService.queryNotPayOrder(asset.getId(), String.valueOf(tblUser.getAppUserId()));
				// 检查当前资产当前用户是否存在未支付完成订单，如果存在则返回现有订单
				if (Objects.nonNull(notPayAssetTransaction)) {
					goodsInfo.setGoodsPrice(notPayAssetTransaction.getPrice());
					log.info("createPayOrder uid:{} notPayAssetTransaction:{}", tblUser.getAppUserId(), JSONUtil.toJsonStr(notPayAssetTransaction));
					TblPayOrder payOrder = iTblPayOrderService.getByBusiness(orderTypeEnum, String.valueOf(notPayAssetTransaction.getOrderId()));
					isNull(payOrder, ErrorMessage.ORDER_CREATE_FAILED);
					log.info("createPayOrder uid:{} current payOrder:{}", tblUser.getAppUserId(), JSONUtil.toJsonStr(payOrder));
					PayCenterArgsResponse response = payCenterHttpService.buildCreateOrderParams(payOrder, tblUser, request.getBackUrl(), assetResponse);
					response.setCreateNewOrder(false);
					return response;
				}
				BigDecimal realUnitPrice = asset.getPrice();
				// 测试用户重置单价
				realUnitPrice = nftOrderService.testUserResetPrice(tblUser.getAppUserId(), realUnitPrice);
				goodsInfo.setGoodsPrice(realUnitPrice);
				BigDecimal realTotalPrice = realUnitPrice.multiply(new BigDecimal(request.getPurchaseAmount()));

				// 风控校验&资金预占
				if (isC2C) {
					payLimitService.preUseAndCheckPayLimit(tblUser, Long.valueOf(asset.getAuthorId()), realTotalPrice);
				}

				// 创建资产交易
				log.info("createPayOrder start create asset transaction uid:{} request:{}", tblUser.getAppUserId(), JSONUtil.toJsonStr(request));
				MakeTransactionRequest makeTransactionRequest = MakeTransactionRequest
						.builder()
						.from(Long.valueOf(asset.getAuthorId()))
						.to(tblUser.getAppUserId())
						.num(request.getPurchaseAmount())
						.assetId(asset.getId())
						.price(realUnitPrice.toPlainString())
						.expireTimeWithLevel(0)
						.orderType(orderTypeEnum.name())
						.fees("0")
						.build();
				makeTransactionRequest.setOrderPrice(realTotalPrice);
				makeTransactionRequest.setBizType("nft");
				AssetTransactionResponse transaction = iInnerService.makeTransaction(makeTransactionRequest);
				isNull(transaction, ErrorMessage.ORDER_CREATE_FAILED, () -> log
						.info("uid:{} makeOrderRequest:{} transaction result is null", tblUser.getAppUserId(), JSONUtil.toJsonStr(makeTransactionRequest)));

				PayAttach payAttach = PayAttach
						.builder()
						.assetId(assetId)
						.goodsId(asset.getGoodsId())
						.purchaseNum(request.getPurchaseAmount())
						.isC2C(isC2C)
						.build();
				// 记录折扣信息、分账信息
				if (isC2C) {
					payAttach.setAssetUserLogId(assetUserLog.getId());
					BigDecimal sellerIncome;
					if (Objects.equals(secondType, SecondEnum.MINT_POWER.getCode())) {
						sellerIncome = realTotalPrice
								.multiply(new BigDecimal(SPLIT_ACCOUNT_PERCENTAGE))
								.divide(BigDecimal.valueOf(100))
								.setScale(2, BigDecimal.ROUND_HALF_UP);
					} else {
						sellerIncome = realTotalPrice
								.multiply(new BigDecimal(SECOND_TRADE_SPLIT_ACCOUNT_PERCENTAGE))
								.divide(BigDecimal.valueOf(100))
								.setScale(2, BigDecimal.ROUND_HALF_UP);
					}
					SplitAccountItem splitAccountItem = new SplitAccountItem(
							Longs.tryParse(asset.getAuthorId()), sellerIncome.toPlainString(), SPLIT_ACCOUNT_PERCENTAGE);
					payAttach.setSplitAccountDetails(Arrays.asList(splitAccountItem));
				}
				// 创建支付订单
				TblPayOrder payOrder = nftOrderService
						.saveUnPayOrder(transaction, orderTypeEnum, String.valueOf(transaction.getOrderId()), realTotalPrice, payAttach);

				// 插入延时取消任务
				nftOrderService.sendPayDelayMessage(payOrder);
				return payCenterHttpService.buildCreateOrderParams(payOrder, tblUser, request.getBackUrl(), assetResponse);
			} else {
				log.warn("createOrder failed. acquire lock on goods and user failed. uid:{} param:{}", tblUser.getAppUserId(), JSONUtil.toJsonStr(request));
			}
		} finally {
			if (lock) {
				redisLockUtil.releaseLock(lockKey, lockValue);
			}
		}
		throw new ChainException(ErrorMessage.UNKNOWN_ERROR);
	}

	private boolean checkAssetBeginSale(AssetExtInfoResponse assetExt) {
		if (assetExt.getPreSellStartDatetime() == null || Objects.equals(assetExt.getPreSellStartDatetime(), 0L)) {
			// 未发布过
			return false;
		}
		return System.currentTimeMillis() > assetExt.getPreSellStartDatetime();
	}

	/**
	 * 判断用户是否拥有预先购买权
	 *
	 * @param appUserId
	 * @return
	 */
	private boolean hasPrePurchaseBenefit(Long appUserId) {
		TblUserBenefitDto result = iInnerService.getLatestUserBenefit(String.valueOf(appUserId), BenefitEnum.PRIORITY_BUY.getCode());
		return result != null && result.getExpireAt().isAfter(LocalDateTime.now());
	}

	/**
	 * 确认支付订单
	 *
	 * @param payOrder
	 * @param callbackMessage
	 * @param appUserId
	 */
	@Override
	public boolean confirmPayOrder(TblPayOrder payOrder, PayCallbackMessage callbackMessage, String appUserId) {
		boolean needRefund = false;
		// 业务订单确认
		TransactionResponse transaction = iInnerService.transactionInfo(Longs.tryParse(payOrder.getBusinessRefId()));
		isNull(transaction, ErrorMessage.ORDER_OPERATE_FAILED, () -> log.error("支付中心支付成功，没有找到对应的业务订单 {}", JSONUtil.toJsonStr(callbackMessage)));
		if (Objects.equals(transaction.getDealState(), DealStateEnum.NOT_PAYED.code())) {
			ConfirmTransactionRequest confirmOrderRequest = ConfirmTransactionRequest
					.builder()
					.orderId(transaction.getOrderId())
					.from(Long.valueOf(transaction.getSellerUserId()))
					.to(Long.valueOf(transaction.getBuyerUserId()))
					.assetId(transaction.getAssetId())
					.actTotalPrice(payOrder.getPayMoney())
					.build();
			try {
				// 记录支付成功
				nftOrderService.savePaySuccessRecord(payOrder, callbackMessage);
				// TODO
				// 三方服务的调用结果不要忽略
				iInnerService.confirmTransaction(confirmOrderRequest);
				// 发送支付成功消息
				rocketMqService.sendPayOrderStatusChangeMsg(payOrder.getPayOrderId());
				return true;
			} catch (ChainException e) {
				log.error("支付中心支付成功，业务订单确认失败 {}", JSONUtil.toJsonStr(callbackMessage), e);
				// 处理失败，取消订单
				doPayCancel(payOrder, "支付中心支付成功，业务订单确认失败. exp:" + e.getMsg() + " callbackMessage:" + JSONUtil.toJsonStr(callbackMessage));
				needRefund = true;
			}
		} else if (Objects.equals(payOrder.getPayStatus(), PayOrderStatusEnum.PAY_CANCEL.getCode())) {
			if (nftOrderService.alreadyApplyRefund(payOrder.getPayOrderId())) {
				log.warn("支付中心支付成功，业务订单已取消，已申请退款 {}", JSONUtil.toJsonStr(callbackMessage));
			} else {
				// 业务订单已取消，向支付中心申请退款
				needRefund = true;
			}
		} else {
			log.warn("支付处理失败，目标订单不是待支付状态！ {}", JSONUtil.toJsonStr(callbackMessage));
		}

		if (needRefund) {
			nftOrderService.startRefundOrder(payOrder, callbackMessage);
		}
		return false;
	}

	@Override
	public void doPayCancel(TblPayOrder payOrder, String remark) {
		// 取消关联业务交易
		TransactionResponse transaction = iInnerService.transactionInfo(Longs.tryParse(payOrder.getBusinessRefId()));
		CancelTransactionRequest cancelTransactionRequest = CancelTransactionRequest
				.builder()
				.orderId(Long.valueOf(payOrder.getBusinessRefId()))
				.from(Long.valueOf(transaction.getSellerUserId()))
				.to(Long.valueOf(transaction.getBuyerUserId()))
				.assetId(transaction.getAssetId())
				.build();
		iInnerService.cancelTransaction(cancelTransactionRequest);

		// 取消payOrder
		TblPayOrderLog payOrderLog = TblPayOrderLog
				.builder()
				.payOrderLogId(IdUtil.getSnowflakeNextId())
				.payStatus(PayOrderStatusEnum.PAY_CANCEL.getCode())
				.payOrderId(payOrder.getPayOrderId())
				.createTime(System.currentTimeMillis())
				.detail(remark)
				.build();
		isFalse(iTblPayOrderLogService.save(payOrderLog), ErrorMessage.ORDER_OPERATE_FAILED);

		isFalse(iTblPayOrderService
				.updatePayStatus(payOrder.getPayOrderId(), PayOrderStatusEnum.UN_PAY, PayOrderStatusEnum.PAY_CANCEL), ErrorMessage.ORDER_OPERATE_FAILED);

		// 发送支付订单状态变更消息
		rocketMqService.sendPayOrderStatusChangeMsg(payOrder.getPayOrderId());
	}

	@Override
	public GoodsInfo getGoodsInfo(TblPayOrder payOrder) {
		GoodsInfo goodsInfo = new GoodsInfo();
		PayAttach payAttach = JSONUtil.toBean(payOrder.getBusinessAttach(), PayAttach.class);
		goodsInfo.setGoodsId(String.valueOf(payAttach.getAssetId()));
		AssetInfoResponse tblAsset = iInnerService.infoWithAssetId(payAttach.getAssetId());
		goodsInfo.setGoodsName(tblAsset.getAssetName());
		goodsInfo.setGoodsNums(payAttach.getPurchaseNum());
		goodsInfo.setGoodsPrice(payOrder.getPayMoney().divide(new BigDecimal(payAttach.getPurchaseNum()), 2, RoundingMode.HALF_UP));
		goodsInfo.setGoodsType("0");
		return goodsInfo;
	}
}
